const str = `## 前言

前段时间上线活动,测试大佬经常找我,我说发生肾么事了?  
原来是出现了好几次前端 _g_mock_g_ 数据带到测试环境的问题,使得测试大佬仇恨值++,差点要来给我一套_g_化挥发_g_加_g_闪电五连鞭_g_.  
每次_g_build_g_的时候,心里都很慌,生怕手写的_g_mock_g_数据带到测试环境.  
我寻思,作为一个程序猿,这种事我应该_g_耗子尾汁_g_,不应该每次都手动的去检查这个问题,所以也一直在思考怎么去解决这个问题. 
经过多方面咨询和自己的思考,最终得到两个比较好的思路:

1. 通过严谨,高度的封装,将所有的请求封装在一起,然后统一在请求层面去配置_g_mock=true_g_ ,使用_g_mock_g_接口,_g_build_g_的时候,强制所有的接口都自动使用正式数据.  

2.  在_g_mock_g_数据处,手动增加约定的特殊注释,然后写一个_g_webpack_g_插件在_g_build_g_的时候去检测是否存在这种注释,如果存在,则直接抛出异常禁止打包.

两者对比下来,其实方法一更好些,因为不需要额外去搞_g_webpack_g_插件,而且代码写起来很好看.  
但是我还是采用的方法二.因为我这边业务不太合适集中把接口封装起来.而且我还有点小私心,想熟悉下_g_wepback_g_的插件开发.

## 理解webpack工作流程

翻了很多资料,搞懂了webpack的大致的工作流程,分享下比较精髓的一段话: 
###### 下面这段引用自_g_Webpack 从零入门到工程化实战 26章_g_  
> Webpack 可以看做是一个工厂车间， _g_plugin_g_ 和_g_loader_g_是车间中的两类机器，工厂有一个车间主任和一个生产车间。车间主任叫_g_Compiler_g_，负责指挥生产车间机器_g_Compilation_g_进行生产劳动，_g_Compilation_g_会首先将进来的原材料（_g_entry_g_）使用一种叫做_g_loader_g_的机器进行加工，生产出来的产品就是_g_Chunk_g_；_g_Chunk_g_生产出来之后，会被组装成_g_Bundle_g_，然后通过一类plugin的机器继续加工，得到最后的_g_Bundle_g_，然后运输到对应的仓库（_g_output_g_）。这个工厂的生产线就是 _g_Tapable_g_，厂子运作的整个流程都是生产线控制的，车间中有好几条生产线，每个生产线有很多的操作步骤（_g_hook_g_），一步操作完毕，会进入到下一步操作，直到生产线全流程完成，再将产出传给下一个产品线处理。整个车间生产线也组成了一条最大的生产线。

## 初步尝试

通过上边的理解,我大概有了思路,我要做的事是在build前拿到由代码组成的字符串,然后对这个字符串通过正则进行遍历,如果查出了约定好的特殊mock注释,则抛出警告打断build,于是写出以下代码:

_g__g__g_js
//webpack.config.js
// 使用插件
const WebpackMocWarnkPlugin = require('webpack-mock-warn')
module.exports = {
  // ...
  plugins: [
    new WebpackMocWarnkPlugin(),
  ],
}
// webpackMocWarnkPlugin/index.js 插件代码

module.exports = class WebpackMockWarnPlugin {
  apply(compiler) {
    compiler.plugin('emit', (compilation, callback) => {
      // compilation.chunks存放着所有的代码块
      compilation.chunks.forEach((chucnk) => {
        const chunkCode = chunk.entryModule._source._value
        //.... 对chunkCode进行遍历匹配
      })
    })
  }
}
_g__g__g_
但是这样写发现webpack警告说_g_compiler.plugin_g_这个_g_api_g_已经不建议使用,需要更换_g_api_g_了.

## 再次尝试

上边_g_webpack_g_工作流程中已经说了,现在(_g_webpack4_g_)应该先去使用_g_compiler_g_合适的钩子,拿到_g_compilation_g_,然后再去用_g_compilation_g_中合适的钩子去拿到代码块,钩子这块还挺绕的,看了好久资料,最后还是求助大佬才解决了疑惑.于是又有写出了以下代码:

_g__g__g_js
// webpackMocWarnkPlugin/index.js 

module.exports = class WebpackMockWarnPlugin {
  apply(compiler) {

    // compilation 这个钩子表示compilation创建成功之后的回调,参数就是热乎乎的compilation
    compiler.hooks.compilation.tap('webpackMockWarnPlugin', (compilation) => {
      compilation.hooks.afterChunks.tap('af', (chunks) => {
        this.testChunk(chunks) // 遍历处理chunks的代码
      })
    })
    // 完成编译和封存编译产出之后的回调
    compiler.hooks.afterCompile.tap('afcompile', this.throwWarn) //如果发现存在特殊标记则抛出错误
  }
}
_g__g__g_
解释下上边使用的_g_compiler.hooks.afterCompile_g_,因为可能会存在多个特殊标记,所以这里需要等所有的代码块都处理完,才能拿到所有的结果,然后在这个钩子里去判断,一起抛出

## 进行优化

主要问题大致解决了,剩余一些边边角角,尽量的去优化一下,贴出最后实现的代码:


_g__g__g_js
// webpackMocWarnkPlugin/index.js 
const { red, cyan, yellow } = require('colorette') //这个插件是用来在控制台上输出多种颜色代码的,这个也是看wepbakc-cli的源码发现的 还是挺好玩的
const wanrns = []
module.exports = class WebpackMockWarnPlugin {

  // 这里允许使用者去自定义特殊标记 或者直接去自己写正则
  constructor(options = {}) {
    this.mockReg = options.mockReg
    this.mockFlag = options.mockFlag || 'mock'
  }
  apply(compiler) {
    compiler.hooks.compilation.tap('webpackMockWarnPlugin', (compilation) => {
      compilation.hooks.afterChunks.tap('af', (chunks) => {
        this.testChunk(chunks)
      })
    })
    compiler.hooks.afterCompile.tap('afcompile', this.throwWarn)
  }
  testChunk(chunks){
    chunks.forEach((chunk) => {
      const reg =
        this.mockReg ||
        new RegExp(_g_(\/\*)\s*_a_this.mockFlag}|\/\/ *_a_this.mockFlag}_g_, 'g')
      const chunkCode = chunk.entryModule._source._value
      // 先生成行数数组.每个元素的index表示当前行数,每个元素的index表示所在行,内容是所在的index
      const rows = [0]
      for (let i = 0; i < chunkCode.length; i++) {
        if (chunkCode[i] === '\n') rows.push(i)
      }
      var regExec = null
      while ((regExec = reg.exec(chunkCode)) !== null) {
        // 若匹配到mock,则取出行数 / 上一行,下三行之内的代码方便检阅
        if (regExec[0]) {
          // 循环遍历所在行数
          for (let index in rows) {
            if (rows[index] >= regExec.index) {
              let content = ''
              let contentWrap = 5
              let endIndex = chunkCode.length
              let startIndex = 0
              for (let i = regExec.index; i >= 0; i--) {
                if (chunkCode[i] === '\n') {
                  startIndex = i
                  break
                }
              }
              for (let i = regExec.index; i <= chunkCode.length - 1; i++) {
                if (contentWrap === 0) {
                  endIndex = i
                  break
                }
                if (chunkCode[i] === '\n') {
                  contentWrap--
                }
              }
              content = chunkCode.slice(startIndex, endIndex)
              wanrns.push({
                row: index || 1,
                path: chunk.entryModule._source._name,
                content,
              })
              break
            }
          }
        }
      }
    })
  }
  throwWarn(){
    if (wanrns.length !== 0) {
      let errorStr = _g__a_red(
        _g_检测到存在_a_wanrns.length}处mock数据,请删除后再次尝试操作:
        如果你想使用自定义捕获mock标记,请配置 mockFlag , 也可以配置 mockReg 自定义捕获mock正则
        _g_
      )}\n_g_
      for (let el of wanrns) {
        errorStr += yellow(
          _g_path: _a_el.path}\nrow: _a_el.row}\ncontent: _a_cyan(el.content)}\n\n_g_
        )       
      }
      console.error(red(errorStr))
      process.exit(1);//结束进程抛出错误
    }
  }
}

_g__g__g_
## 发布至npm
修改下pagejson:

_g__g__g_json
{
  "name": "xxx",
  "version": "1.0.0",
  "description": "When build, it detects the mock data and throws a warning",
  "main": "./src/index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "publishConfig": {
    "registry": "http://registry.npmjs.org"
  },
  "keywords": [
    "webpack",
    "mock"
  ],
  "author": "xxx <xxxx.com>",
  "license": "ISC",
  "dependencies": {
    "colorette": "^1.2.1",
    "webpack-cli": "^4.2.0"
  }
}
_g__g__g_
再编辑下readme文件  
_g_npm publish_g_  
大功告成!
## 结语

其实到最后,还遗留了2个问题:
1. 现在最后是调用_g_node_g_的_g_process.exit(1)_g_直接在命令行抛出错误,这很不优雅,很难被称为_g_warn_g_,我其实理想的是直接终止掉_g_webpack_g_的打包而不抛出错误,但是没找到好的解决方案.
2. 对_g_html/css_g_注释进行适配,这个暂时没有需求,所以没有费力去搞,而且中间会涉及到_g_loader_g_转义后的_g_html/css_g_,而不是编译前的,应该也要费一番力气.

这个小东西也花费了我好几天的摸鱼时间,开发过程中遇到的问题,比上边描述的要多得多.   

我个人研究东西总是要有一个源头,就是说我要先知道我想干嘛,然后再去由这个进一步去研究.如果直接去学习某种技术,总是会迷失.从业务问题出发,到开发_g_webpack_g_插件去解决问题.这种体验还是挺好的. 

当然,以小见大,理解了_g_webpack_g_的工作流程,也算有所收获.

深刻感觉到了,提出一个好问题,或许比解决这个问题更重要.

### 我是菜菜驴,江湖人称驴渣.感谢你阅读我的分享!
`

let strrep = str.replace(/_g_/g, '`')
strrep = strrep.replace(/_a_/g, '${')
export default strrep
